import { useStatusBar } from "../scrapbox-userscript-std/dom.ts"; import { getWebPageTitle, getTweetInfo } from "../scrapbox-userscript-std/rest.ts"; import type { TweetInfo } from "../scrapbox-jp%2Ftypes/rest.ts"; const checkRegExp = /(https?:\/\/\S+)/; export const hasURL = (text: string): boolean => checkRegExp.test(text); export const convert = async (text: string): Promise => { if (!hasURL(text)) return text; const { render, dispose } = useStatusBar(); render( { type: "spinner" }, { type: "text", text: "convert URLs..."}, ); let count = 0; try { const result = await Promise.all( text.split(checkRegExp).map(async (fragment) => { if (!/^https?:\/\/\S+/.test(fragment)) return fragment; let url: URL; try { url = new URL(fragment); } catch(e: unknown) { if (e instanceof TypeError) return fragment; throw e; } if (isTwitterURL(url)) { const result = await getTweetInfo(url); if (!result.ok) return fragment; count++; return formatTweet(url, result.value); } const result = await getWebPageTitle(url); if (!result.ok) return fragment; count++; const title = result.value .replace(/\s/g, " ") // スペースと改行を全て半角スペースにする .replaceAll("[", "[") .replaceAll("]", "]"); return `[${url} ${title}]`; }) ); render( { type: "check-circle" }, { type: "text", text: `Converted ${count} URLs.`}, ); return result.join(""); } catch(e: unknown) { render( { type: "exclamation-triangle" }, { type: "text", text: e instanceof Error ? `${e.name} ${e.message}` : `Unknown error! (see developper console)`, }, ); console.error(e); throw e; } finally { setTimeout(() => dispose(), 1000); } }; const isTwitterURL = (url: string | URL) => /^https:\/\/(?:www\.|mobile\.|m\.|)twitter\.com\/([A-Za-z0-9_]*)\/(?:status|statuses)\/\d+/.test(url.toString()); const escapeForEmbed = (text: string) => text .replace(/\b/gm, "") .replace(/[\s\r\n\u2028\u2029]+/gm, " ") .replace(/\s*[[\]`]\s*/g, " ") .trim(); const formatTweet = (url: URL, tweet: TweetInfo) => [ `[${escapeForEmbed(tweet.screenName)}(@${escapeForEmbed(tweet.userName)}) ${url.origin}${url.pathname}]`, ...tweet.description.split("\n").map((line) => `> ${escapeForEmbed(line)}`), ...(tweet.images.length > 0 ? [`> ${tweet.images.map((image) => `[${image}]`)}`] : []), ].join("\n");